package registry;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.ListIterator;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import common.Util;

public class MasterService {
	private RegistryObject registry;
	private ArrayList<WorkerObject> workerList = new ArrayList<WorkerObject>();
	private ArrayList<String> resultList = new ArrayList<String>();

	public MasterService (ArrayList<WorkerObject> workerList) {
		this.workerList = workerList;
	}

	public MasterService (RegistryObject registry, ArrayList<WorkerObject> workerList) {
		this.registry = registry;
		this.workerList = workerList;
	}

	public void register(){

		System.out.println("SYSINFO - Register Service start.");
		try {
			System.out.println("SYSINFO - Register Service works on address: "+InetAddress.getLocalHost().getHostAddress()+":"+registry.getRegistryUDPPort());
		} catch (UnknownHostException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		DatagramSocket aSocket = null;

		try {
			aSocket = new DatagramSocket(registry.getRegistryUDPPort());
			byte buf[] = new byte[1024];
			Long serverLocalTime = null;

			while (true) {
				System.out.println("SYSINFO - Waiting for registration request.");
				DatagramPacket request = new DatagramPacket(buf, buf.length);
				aSocket.receive(request);
				serverLocalTime = getTime();
				System.out.println("SYSINFO - Received registration request.");
				String registerXML = new String(buf, 0, request.getLength());
//				System.out.println("DEBUG - XML Request: "+regRequest);
				
				JAXBContext context = JAXBContext.newInstance(RegisterRequestObject.class);
				Unmarshaller u = context.createUnmarshaller();
			    StringBuffer xmlStr = new StringBuffer(registerXML);
			    RegisterRequestObject registerRequest = (RegisterRequestObject) u.unmarshal( new StreamSource( new StringReader( xmlStr.toString() ) ) );
			    
			    System.out.println("Registration request information:");
			    if (registerRequest.getFrom().equals("Worker")&&registerRequest.getType().equals("Register")) {
			    	WorkerObject worker = registerRequest.getWorker();
			    	worker.setRegTime(serverLocalTime);
			    	
			    	System.out.println("DEBUG - ip = " + worker.getIp());
					System.out.println("DEBUG - port = " + worker.getPort());
					System.out.println("DEBUG - price = " + worker.getPrice());
					System.out.println("DEBUG - load = " + worker.getLoad());
					System.out.println("DEBUG - time = " + worker.getRegTime());
					
					System.out.println("SYSINFO - Check Worker list before register a new Worker address.");
					if (workerList.isEmpty()) {
//						System.out.println("DEBUG - Worker list is empty.");
						workerList.add(worker);
						System.out.println("SYSINFO - Register the new Worker address.");
					} else {
//						System.out.println("DEBUG - Worker list is nonempty. Start to scan the list......");
						ListIterator<WorkerObject> iter = workerList.listIterator();
						boolean sameIndicator = false;
						while (iter.hasNext()) {
							WorkerObject workerTemp = iter.next();
							if (workerTemp.getIp().equals(worker.getIp()) && workerTemp.getPort() == worker.getPort()) {
								System.out.println("SYSINFO - Found a duplicated Worker address: "
										+workerTemp.getRegTime()
										+ " / " + workerTemp.getIp() + ":"
										+ workerTemp.getPort());
								WorkerObject renewWorker = new WorkerObject(worker.getIp(), worker.getPort(), worker.getPrice(), worker.getLoad(), serverLocalTime);
								iter.set(renewWorker);
								System.out.println("SYSINFO - Renew the register time for the duplicated Worker address.");
								System.out.println("SYSINFO - Renewed Worker information: "
										+ renewWorker.getRegTime()
										+ " / " + workerTemp.getIp() + ":"
										+ workerTemp.getPort());
								sameIndicator = true;
							}
						}
						if (sameIndicator == false) {
//							System.out.println("DEBUG - No duplicated Worker address was found.");
							WorkerObject newWorker = new WorkerObject(worker.getIp(), worker.getPort(), worker.getPrice(), worker.getLoad(), serverLocalTime);
							iter.add(newWorker);
							System.out.println("SYSINFO - Register the new Worker address.");
						}
					}
			    } else {
					System.out.println("SYSINFO - Registration Request is not from Worker.");
				}
			}
		} catch (SocketException e){System.out.println("Socket:" + e.getMessage());
		} catch (IOException e){System.out.println("IO:" + e.getMessage());
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if(aSocket != null) {
				aSocket.close();
				System.out.println("SYSINFO - Client TCP connection is close.");
			}
		}
	}

	//dont need this
	public void replyList(){

		System.out.println("SYSINFO - List Reply service start.");
		try {
			System.out.println("SYSINFO - List Reply service works on address: "+InetAddress.getLocalHost().getHostAddress()+":"+registry.getRegistryTCPPort());
		} catch (UnknownHostException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		try{
			//			SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
			//			SSLServerSocket listenSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(managerTCPPort);
			ServerSocket listenSocket = new ServerSocket(registry.getRegistryTCPPort());

			while (true) {
				Socket clientSocket = listenSocket.accept();
				//				SSLSocket clientSocket = (SSLSocket) listenSocket.accept();
				System.out.println("SYSINFO - Accept Client TCP connection.");

				ReplyEachListRequestThread c = new ReplyEachListRequestThread(clientSocket, workerList);
				c.start();
			}
		}catch(IOException e){System.out.println("Listen:"+e.getMessage());}
	}

	public String createListReplyXML() throws Exception {

		// Create XML document for Replying List
		DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = null;
		try {
			db = dbfactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Document outDoc = db.newDocument();
		outDoc.setXmlVersion("1.0");
		Element outRoot = outDoc.createElement("CloudServer");
		outRoot.setAttribute("type", "ListReply");
		outRoot.setAttribute("version", "1");

		Element timestampElement = outDoc.createElement("Timestamp");
		Long time = getTime();
		timestampElement.appendChild(outDoc.createTextNode(time.toString()));
		outRoot.appendChild(timestampElement);

		if (!workerList.isEmpty()) {
			Element workerListElement = outDoc.createElement("WorkerList");
			Iterator<WorkerObject> iter = workerList.iterator();
			while (iter.hasNext()) {
				WorkerObject worker = iter.next();

				Element workerElement = outDoc.createElement("Worker");

				Element ipAddressElement = outDoc.createElement("IP");
				ipAddressElement.appendChild(outDoc.createTextNode(worker.getIp()));
				workerElement.appendChild(ipAddressElement);

				Element portElement = outDoc.createElement("Port");
				portElement.appendChild(outDoc.createTextNode(Integer.toString(worker.getPort())));
				workerElement.appendChild(portElement);

				Element priceElement = outDoc.createElement("Price");
				priceElement.appendChild(outDoc.createTextNode(Integer.toString(worker.getPrice())));
				workerElement.appendChild(priceElement);

				Element loadElement = outDoc.createElement("Load");
				loadElement.appendChild(outDoc.createTextNode(Integer.toString(worker.getLoad())));
				workerElement.appendChild(loadElement);

				workerListElement.appendChild(workerElement);
			}
			outRoot.appendChild(workerListElement);
		}else{
			System.out.println("workerList empty!!!");
		}

		outDoc.appendChild(outRoot);
		String xmlStr = Util.docToString(outDoc);
		//		System.out.println("DEBUG - XML Request: "+xmlStr);

		return xmlStr;
	}

	public Long getTime() {
		Date date = new Date();
		return date.getTime();
	}

	public void workerTimeout() {
		System.out.println("SYSINFO - Worker timeout service start.");
		Long currentTime = getTime();
		ListIterator<WorkerObject> iter = workerList.listIterator();
		WorkerObject worker = null;
		while (iter.hasNext()) {
			worker = iter.next();
			if ((currentTime - worker.getRegTime()) > 70000) {
				iter.remove();
				System.out.println("SYSINFO - Address "+worker.getIp()+":"+worker.getPort()+" has been deleted.");
			}
		}
		System.out.println("SYSINFO - Worker timeout checking is finished.");
	}
}

class ReplyEachListRequestThread extends Thread{
	DataInputStream dataIn;
	DataOutputStream dataOut;
	Socket clientSocket;
	ArrayList<WorkerObject> workerList;
	MasterService registryService;

	public ReplyEachListRequestThread (Socket aClientSocket, ArrayList<WorkerObject> aWorkerList){
		try{
			clientSocket = aClientSocket;
			workerList = aWorkerList;
			registryService = new MasterService(workerList);

			InputStream in = clientSocket.getInputStream();
			dataIn = new DataInputStream(in);

			OutputStream out = clientSocket.getOutputStream();
			dataOut = new DataOutputStream(out);
		} catch(IOException e){System.out.println("Conection:"+e.getMessage());}
	}

	public void run(){
		try{
			String listRequest = dataIn.readUTF();
			System.out.println("SYSINFO - Received list service request.");
			System.out.println("Request XML: "+listRequest);

			Document inDoc = Util.stringToDoc(listRequest);
			Element inRoot = inDoc.getDocumentElement();

			// check whether the request is from Client
			if (inRoot.getAttribute("type").equals("List")) {
				System.out.println("DEBUG - It is a list request.");
				String reply = null;

				// leave for handling Virtual Worker Cloud
				if (inRoot.getElementsByTagName("VWorker").getLength()==0) {
					reply = registryService.createListReplyXML();
				} else { // leave for future use
					//String ip = inRoot.getElementsByTagName("IP").item(0).getTextContent();
					//String port = inRoot.getElementsByTagName("Port").item(0).getTextContent();
					//int workerNum = Integer.parseInt(inRoot.getElementsByTagName("Counter").item(0).getTextContent());
					//					reply = cloudServerService.createListReplyXML(ip, port, ripnum);
					reply = registryService.createListReplyXML();
				}				

				System.out.println("Reply XML: "+reply);
				dataOut.writeUTF(reply);
				System.out.println("SYSINFO - Send reply.");
				dataOut.close();
			}
			else {
				System.out.println("SYSINFO - The request is not a ListRequest. No action will be performed.");
			}
			clientSocket.close();
			System.out.println("SYSINFO - Rigistry TCP connection is close.");
		} catch(EOFException e) {System.out.println("EOF:"+e.getMessage());
		} catch(IOException e) {System.out.println("IO:"+e.getMessage());
		} catch (Exception e) {e.printStackTrace();
		} finally{
			try{clientSocket.close();
			} catch(IOException e){/*close failed*/}}
	}
}
